package com.oj.security.executor;

import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.Comparator;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * Docker容器中代码执行的基类
 * 使用虚拟线程和信号量控制并发
 */
@Slf4j
@Component
public abstract class DockerExecutor implements LanguageExecutor {
    
    @Value("${executor.scripts-dir}")
    private String scriptsDir;
    
    @Value("${executor.temp-dir}")
    private String tempDir;
    
    @Value("${executor.timeout:5}")
    private int executionTimeout;
    
    @Value("${executor.max-concurrent:10}")
    private int maxConcurrent;
    
    private static final int OUTPUT_LIMIT = 1000; // 输出行数限制
    private static final int MAX_CODE_LENGTH = 65536; // 代码长度限制64KB
    
    private final Semaphore executionSemaphore;
    private final ExecutorService executor;
    
    public DockerExecutor() {
        this.executionSemaphore = new Semaphore(maxConcurrent);
        this.executor = Executors.newVirtualThreadPerTaskExecutor();
    }

    /**
     * 在Docker容器中执行代码
     */
    protected ExecutionResult runInDocker(String command, String code) {
        // 验证代码长度
        if (code == null || code.length() > MAX_CODE_LENGTH) {
            return new ExecutionResult(false, null, "代码长度超出限制");
        }
        
        try {
            // 尝试获取信号量
            if (!executionSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
//                return new ExecutionResult(false, null, "请求太频繁，请稍后重试");
            }

            try {
                return CompletableFuture.supplyAsync(
                    () -> executeInDocker(command, code),
                    executor
                ).get(executionTimeout + 1, TimeUnit.SECONDS);
            } finally {
                executionSemaphore.release();
            }
        } catch (TimeoutException e) {
            return new ExecutionResult(false, null, "执行超时");
        } catch (Exception e) {
            return new ExecutionResult(false, null, "执行错误: " + e.getMessage());
        }
    }

    private ExecutionResult executeInDocker(String command, String code) {
        Process process = null;
        Path codeFile = null;
        
        try {
            // 获取脚本路径
            String runCmd = getScriptPath();
            
            // 创建临时文件
            codeFile = createCodeFile(code);
            
            // 构建进程
            ProcessBuilder pb = new ProcessBuilder(
                runCmd,
                command,
                codeFile.toString()
            );

            // 设置环境变量
            pb.environment().put("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
            pb.redirectErrorStream(true);

            // 启动进程
            process = pb.start();

            // 读取输出
            String output = readProcessOutput(process);
            
            // 等待完成
            boolean finished = process.waitFor(executionTimeout, TimeUnit.SECONDS);
            if (!finished) {
                return new ExecutionResult(false, null, "执行超时");
            }

            // 检查退出码
            int exitCode = process.exitValue();
            return exitCode == 0
                ? new ExecutionResult(true, output, null)
                : new ExecutionResult(false, null, "执行失败: " + output);

        } catch (Exception e) {
            log.error("执行代码失败", e);
            return new ExecutionResult(false, null, "执行错误: " + e.getMessage());
        } finally {
            // 清理资源
            if (process != null) {
                process.destroyForcibly();
            }
            if (codeFile != null) {
                try {
                    Files.deleteIfExists(codeFile);
                } catch (IOException e) {
                    // 忽略删除失败
                }
            }
        }
    }

    private String getScriptPath() {
        boolean isRunningFromJar = Thread.currentThread().getContextClassLoader()
                .getResource("org/springframework/boot/loader/Launcher.class") != null;
                //todo 带完善路径
        return  scriptsDir + "/run-code.sh";
//        return isRunningFromJar
//            ? scriptsDir + "/run-code.sh"
//            : getClass().getClassLoader().getResource("scripts/run-code.sh").getPath();
    }

    private String readProcessOutput(Process process) throws IOException {
        try (BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
            return reader.lines()
                .limit(OUTPUT_LIMIT)
                .collect(Collectors.joining("\n"));
        }
    }

    private Path createCodeFile(String code) throws IOException {
        Path tempPath = Paths.get(tempDir);
        if (!Files.exists(tempPath)) {
            Files.createDirectories(tempPath);
        }
        
        String fileName = getFileName(code);
        Path codeFile = tempPath.resolve(fileName + getFileExtension());
        Files.writeString(codeFile, code, StandardCharsets.UTF_8);
        return codeFile;
    }

    protected String getFileName(String code) {
        return "Main";
    }

    protected abstract String getFileExtension();

    @PreDestroy
    public void cleanup() {
        // 关闭执行器
        executor.shutdown();
        try {
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }

        // 清理临时目录
        try {
            Path tempPath = Paths.get(tempDir);
            if (Files.exists(tempPath)) {
                Files.walk(tempPath)
                    .sorted(Comparator.reverseOrder())
                    .forEach(path -> {
                        try {
                            Files.delete(path);
                        } catch (IOException e) {
                            // 忽略删除失败
                        }
                    });
            }
        } catch (IOException e) {
            // 忽略清理失败
        }
    }
} 